package com.summer.bilibili.aop.login;

import com.summer.bilibili.exception.CustomException;
import com.summer.bilibili.util.RedisUtils;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * @classname LoginLimitAspect
 * @description 自定义注解 @LoginLimit标记需要限流的登录方法
 * @date 2025/8/31 21:30
 * @author: summer
 */
@Aspect // 声明为 AOP 切面类
@Component // 纳入 Spring容器管理
@RequiredArgsConstructor // Lombok生成包含finalshell字段的构造函数（用于注入RedisUtils）
public class LoginLimitAspect {
    private static final String REDIS_KEY_PREFIX = "login:limit:";
    private final RedisUtils redisUtils;

    @Pointcut("@annotation(com.summer.bilibili.aop.login.LoginLimit)") // 定义切入点
    public void pointCut () {
    }


    @Around("pointCut()")// 环绕通知实现核心限流逻辑
    public Object around ( ProceedingJoinPoint joinPoint ) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature ( ); // 获取方法签名
        Method method = methodSignature.getMethod ( ); // 反射获取 Method 对象
        // 获取方法上的 注解
        LoginLimit loginLimit = method.getAnnotation ( LoginLimit.class );
        // 创建SpEL 的求职上下文（存放变量）
        StandardEvaluationContext context = new StandardEvaluationContext ( );
        // 将前端传入的每个参数放入上下文中
        Parameter[] parameters = method.getParameters ( );
        for ( int i = 0 ; i < parameters.length ; i++ ) {
            context.setVariable ( parameters[ i ].getName ( ) , joinPoint.getArgs ( )[ i ] );
        }
        // 计算表达式后的结果
        Object value = new SpelExpressionParser ( ) // 创建解析器
                .parseExpression ( loginLimit.value ( ) ) // 解析注解中的SpEL字符串
                .getValue ( context );// 在给定的上下文中求职
        String key = REDIS_KEY_PREFIX + "username:" + value; // 拼接完整的 Redis 键

/*        // 使用缓存提升解析性能
        private static final Map<String, Expression> expressionCache = new ConcurrentHashMap<>();

        Expression expr = expressionCache.computeIfAbsent(
                loginLimit.value(),
                exprStr -> new SpelExpressionParser().parseExpression(exprStr)
        );*/

        int count = Integer.parseInt ( redisUtils.getString ( key , "0" ) ); // 计数获取
        Duration lockTime = Duration.ofSeconds ( loginLimit.lockTime ( ) ); // 将注解配置的秒数转为 Duration 对象
        // 限流判断
        if ( count >= loginLimit.maxTryTimes ( ) ) {
            Long ttl = redisUtils.ttl ( key , TimeUnit.SECONDS );
            throw new CustomException ( "登录过于频繁，请稍等" + ttl + "秒" );
        }
/*// 使用 Pipeline 减少网络开销
        try (Jedis jedis = jedisPool.getResource()) {
            Pipeline p = jedis.pipelined();
            p.incrBy(key, 1);
            p.expire(key, lockTime.getSeconds());
            p.sync();
        }*/

        try {
            Object result = joinPoint.proceed ( ); // 执行原始登录逻辑
            // 登陆成功清楚失败记录
            redisUtils.del ( key );
            return result;
        } catch (Throwable e) {
            for ( Class < ? extends Exception > exceptionClass : loginLimit.exception ( ) ) {
                if ( exceptionClass.isAssignableFrom ( e.getClass ( ) ) ) {
                    redisUtils.increment ( key , 1 ); // 原子增加失败计数
                    redisUtils.expire ( key , lockTime ); // 设置锁定时间
                    break;
/*                    // 根据失败次数动态增加锁定时间
                    int dynamicLockTime = lockTime.getSeconds() * (count + 1);
                    redisUtils.expire(key, Duration.ofSeconds(dynamicLockTime));*/
                }
            }
            throw e;
        }

    }

}
